package com.luo.demo.dynamic.tenant.service.impl;

import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import com.luo.demo.dynamic.tenant.context.TenantContext;
import com.luo.demo.dynamic.tenant.entity.MyTenant;
import com.luo.demo.dynamic.tenant.service.IMyTenantService;
import com.luo.demo.dynamic.tenant.service.ITenantDsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.sql.DataSource;

/**
 * 租户切换数据源 - 服务实现类
 *
 * @author luohq
 * @date 2022-08-08 16:54
 */
@Service
@Slf4j
public class TenantDsServiceImpl implements ITenantDsService {

    @Resource
    private DynamicRoutingDataSource dataSource;
    @Resource
    private DefaultDataSourceCreator dataSourceCreator;

    @Resource
    private IMyTenantService myTenantService;

    /**
     * 根据租户ID切换数据源
     *
     * @param tenantId 租户ID
     */
    @Override
    public void changeDsByTenantId(String tenantId) {
        //当前租户ID对应的数据源已存在，则直接切换
        if (this.existDsInMemory(tenantId)) {
            //切换数据源
            this.changeTenantDs(tenantId);
            return;
        }

        //若当前租户ID对应的数据源在内存中不存在，则通过租户ID查询租户对应的数据源连接信息
        DataSource dataSource = this.convertTenantIdToDataSource(tenantId);
        //租户对应的数据源连接信息存在，则动态添加数据源并切换
        if (null != dataSource) {
            //动态添加数据源
            this.dataSource.addDataSource(tenantId, dataSource);
            //切换数据源
            this.changeTenantDs(tenantId);
            return;
        }
        //否则数据源信息不存在，则使用默认数据源 或者 抛出异常结束处理流程
        //throw new RuntimeException("租户ID[" + tenantId + "]对应的租户信息不存在！");
    }

    /**
     * 切换租户对应的数据源
     *
     * @param tenantId 租户ID即对应数据源名称
     */
    private void changeTenantDs(String tenantId) {
        log.debug("切换数据源：{}", tenantId);
        //设置租户上下文
        TenantContext.setTenant(tenantId);
        //根据tenantId切换数据源
        DynamicDataSourceContextHolder.push(tenantId);
    }

    /**
     * 根据租户ID查询数据源连接信息，并生成数据源
     *
     * @param tenantId
     * @return
     */
    private DataSource convertTenantIdToDataSource(String tenantId) {
        MyTenant myTenant = null;
        log.debug("find db tenant info by tenantId:{}, result: {}", tenantId, myTenant);
        //租户为空则直接返回空
        if (!StringUtils.hasText(tenantId)
                || null == (myTenant = this.myTenantService.getById(Long.valueOf(tenantId)))) {
            return null;
        }

        DataSourceProperty dataSourceProperty = new DataSourceProperty();
        dataSourceProperty.setUrl(myTenant.getDbUrl());
        dataSourceProperty.setUsername(myTenant.getDbUsername());
        dataSourceProperty.setPassword(myTenant.getDbPassword());
        dataSourceProperty.setDriverClassName(myTenant.getDbDriverClassName());
        //当前工程中仅提供HikariCP连接池依赖，所以默认使用DefaultDataSourceCreator -> HikariDataSourceCreator进行创建
        //可通过如下HikariCpConfig定制连接池配置
        //dataSourceProperty.setHikari(new HikariCpConfig());
        //其他如使用Druid连接池配置
        //dataSourceProperty.setDruid(new DruidConfig());
        DataSource dataSource = this.dataSourceCreator.createDataSource(dataSourceProperty);
        return dataSource;
    }

    /**
     * 当前应用是否已在内存中加载过此数据源
     *
     * @param dsName 数据源名称
     * @return
     */
    @Override
    public Boolean existDsInMemory(String dsName) {
        return StringUtils.hasText(dsName) && this.dataSource.getDataSources().containsKey(dsName);
    }

    /**
     * 清理当前调用上下文中的数据源缓存
     */
    @Override
    public void clearDsContext() {
        //清空当前线程数据源
        DynamicDataSourceContextHolder.clear();
        TenantContext.remove();
    }

    /**
     * 移除对应的数据源信息
     *
     * @param dsName 数据源名称
     */
    @Override
    public void removeDs(String dsName) {
        //动态移除数据源
        this.dataSource.removeDataSource(dsName);
    }

}
